package com.example.fileuploaddownload.service.impl;

import com.example.fileuploaddownload.entity.FileChunk;
import com.example.fileuploaddownload.entity.FileEntity;
import com.example.fileuploaddownload.mapper.FileMapper;
import com.example.fileuploaddownload.service.FileChunkService;
import com.example.fileuploaddownload.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.*;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 文件服务实现类
 * 实现文件上传、断点续传、下载等核心业务逻辑
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService {



    // 静态初始化块，用于打印配置信息
    static {
        log.info("初始化FileServiceImpl服务");
    }

    @Autowired
    private FileMapper fileMapper;
    
    @Autowired
    private FileChunkService fileChunkService;

    @Value("${file.upload-dir}")
    private String uploadDir;

    // 存储上传进度的临时缓存
    private final Map<String, Double> uploadProgressMap = new ConcurrentHashMap<>();
    
    // 初始化方法，用于验证上传目录
    @PostConstruct
    public void init() {
        log.info("上传目录配置: {}", uploadDir);
        File dir = new File(uploadDir);
        log.info("上传目录存在: {}, 可写: {}, 绝对路径: {}", 
                dir.exists(), dir.canWrite(), dir.getAbsolutePath());
        // 尝试创建目录
        if (!dir.exists()) {
            boolean created = dir.mkdirs();
            log.info("创建上传目录结果: {}, 现在存在: {}", created, dir.exists());
        }
    }

    @Override
    public FileEntity uploadFile(MultipartFile file, Integer chunkIndex, Integer totalChunks, String identifier) throws IOException {
        log.info("上传文件分片: {}, 总分片: {}, 标识符: {}", chunkIndex, totalChunks, identifier);

        // 确保上传目录存在
        File uploadDirectory = new File(uploadDir);
        if (!uploadDirectory.exists()) {
            uploadDirectory.mkdirs();
        }

        // 创建临时目录存储分片 - 使用Path类进行安全的路径处理
        File uploadDirFile = new File(uploadDir);
        String normalizedUploadDir = uploadDirFile.getAbsolutePath();
        
        Path tempDirPath = Paths.get(normalizedUploadDir, "temp", identifier);
        String tempDir = tempDirPath.toString();
        File tempDirectory = new File(tempDir);
        if (!tempDirectory.exists()) {
            log.info("创建临时目录: {}", tempDir);
            boolean created = tempDirectory.mkdirs();
            log.info("临时目录创建结果: {}, 存在状态: {}", created, tempDirectory.exists());
        }

        // 保存分片到临时目录
        Path chunkFilePathObj = Paths.get(tempDir, String.valueOf(chunkIndex));
        String chunkFilePath = chunkFilePathObj.toString();
        File chunkFile = new File(chunkFilePath);
        log.info("准备保存分片: {}, 目标路径: {}, 文件大小: {}", chunkIndex, chunkFilePath, file.getSize());
        file.transferTo(chunkFile);
        log.info("分片保存成功: {}, 文件是否存在: {}, 文件大小: {}", chunkIndex, chunkFile.exists(), chunkFile.length());

        // 保存分片信息到数据库
        FileChunk fileChunk = new FileChunk();
        fileChunk.setIdentifier(identifier);
        fileChunk.setChunkIndex(chunkIndex);
        fileChunk.setChunkSize(file.getSize());
        fileChunk.setChunkPath(chunkFilePath);
        fileChunk.setMd5(calculateChunkMd5(chunkFilePathObj));
        fileChunkService.saveChunk(fileChunk);

        // 更新上传进度
        updateUploadProgress(identifier, chunkIndex, totalChunks);

        // 检查数据库中是否已有该文件信息
        FileEntity existingFile = fileMapper.findByFileMd5(identifier);
        if (existingFile != null) {
            Double progress = uploadProgressMap.getOrDefault(identifier, 0.0);
            existingFile.setUploadProgress(progress);
            
            // 更新已上传分片数
            int currentUploadedChunks = existingFile.getUploadedChunks() + 1;
            existingFile.setUploadedChunks(currentUploadedChunks);
            
            // 更新数据库中的进度和分片数
            fileMapper.updateById(existingFile);
            
            return existingFile;
        }

        // 如果是第一个分片，创建文件记录
        if (chunkIndex == 0) {
            FileEntity newFile = new FileEntity();
            newFile.setFileMd5(identifier);
            newFile.setIdentifier(identifier);  // 设置identifier字段
            newFile.setTotalChunks(totalChunks);  // 设置总分片数
            newFile.setUploadedChunks(1);  // 已上传1个分片
            newFile.setUploadStatus(0);
            newFile.setUploadProgress(uploadProgressMap.getOrDefault(identifier, 0.0));
            
            // 为必填字段设置默认值，避免违反数据库约束
            newFile.setFileName("temp_" + identifier);  // 临时文件名
            newFile.setOriginalName("temp_" + identifier); // 临时原始名称
            newFile.setFilePath(tempDir);  // 使用临时目录作为文件路径
            newFile.setFileSize(0L);  // 初始大小为0
            
            fileMapper.insert(newFile);
            
            return newFile;
        }

        return null;
    }

    @Override
    @Transactional
    public FileEntity mergeChunks(String identifier, String fileName, String contentType) throws IOException {
        log.info("开始合并文件分片: {}, 文件名: {}", identifier, fileName);
        
        // 验证所有分片是否都已上传
        int[] uploadedChunks = fileChunkService.getUploadedChunkIndexes(identifier);
        FileEntity fileEntity = fileMapper.findByFileMd5(identifier);
        if (fileEntity != null && fileEntity.getTotalChunks() != null) {
            if (uploadedChunks.length != fileEntity.getTotalChunks()) {
                log.error("分片数量不匹配，期望: {}, 实际: {}", fileEntity.getTotalChunks(), uploadedChunks.length);
                throw new IOException("分片数量不匹配，无法合并文件");
            }
        }
        
        // 验证并规范化上传目录路径 - 使用更安全的路径处理方式
        File uploadDirFile = new File(uploadDir);
        String normalizedUploadDir = uploadDirFile.getAbsolutePath();
        if (!normalizedUploadDir.endsWith(File.separator)) {
            normalizedUploadDir = normalizedUploadDir + File.separator;
        }
        
        log.info("使用的上传目录(规范化后): {}", normalizedUploadDir);

        // 构建临时目录和最终文件路径 - 使用Path类进行安全的路径组合
        Path tempDirPath = Paths.get(normalizedUploadDir, "temp", identifier);
        String tempDir = tempDirPath.toString();
        
        // 保持原文件名不变，只在文件名冲突时添加UUID以避免覆盖
        String finalFileName = fileName;
        Path finalFilePathObj = Paths.get(normalizedUploadDir, finalFileName);
        // 检查文件是否已存在，如果存在则添加UUID后缀
        int counter = 1;
        while (Files.exists(finalFilePathObj)) {
            String baseName = finalFileName.contains(".") ? finalFileName.substring(0, finalFileName.lastIndexOf('.')) : finalFileName;
            String extension = finalFileName.contains(".") ? finalFileName.substring(finalFileName.lastIndexOf('.')) : "";
            finalFileName = baseName + "_" + UUID.randomUUID().toString().substring(0, 8) + extension;
            finalFilePathObj = Paths.get(normalizedUploadDir, finalFileName);
            counter++;
            // 防止无限循环
            if (counter > 10) break;
        }
        String finalFilePath = finalFilePathObj.toString();
        
        log.info("临时目录: {}, 最终文件路径: {}", tempDir, finalFilePath);
        log.info("文件保存路径完整检查: {}", new File(finalFilePath).getAbsolutePath());

        // 确保目标目录存在，使用mkdirs确保所有父目录都被创建
        File finalDirectory = new File(uploadDir);
        if (!finalDirectory.exists()) {
            log.info("创建上传目录: {}", uploadDir);
            boolean created = finalDirectory.mkdirs();
            log.info("上传目录创建结果: {}", created);
            if (!created) {
                throw new IOException("无法创建上传目录: " + uploadDir);
            }
        }
        
        // 检查目录权限
        if (!finalDirectory.canWrite()) {
            log.error("上传目录无写入权限: {}", uploadDir);
            throw new IOException("上传目录无写入权限: " + uploadDir);
        }

        // 获取所有分片文件并按索引排序
        File tempDirFile = new File(tempDir);
        log.info("临时目录存在: {}, 可读写: {}, 可执行: {}", tempDirFile.exists(), tempDirFile.canRead(), tempDirFile.canWrite());
        
        File[] chunkFiles = tempDirFile.listFiles();
        if (chunkFiles == null) {
            log.error("无法访问临时目录或目录不存在: {}", tempDir);
            throw new IOException("无法访问临时目录: " + tempDir);
        }
        
        if (chunkFiles.length == 0) {
            log.error("临时目录中没有分片文件: {}", tempDir);
            throw new IOException("没有找到文件分片");
        }
        
        log.info("找到 {} 个分片文件", chunkFiles.length);

        Arrays.sort(chunkFiles, Comparator.comparingInt(f -> Integer.parseInt(f.getName())));

        // 合并分片到最终文件
        log.info("开始合并分片到最终文件: {}", finalFilePath);
        // 使用传统的FileOutputStream方式，更稳定且有更好的错误处理
        try (FileOutputStream fos = new FileOutputStream(finalFilePath);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            log.info("文件输出流创建成功");
            
            for (File chunkFile : chunkFiles) {
                log.info("处理分片文件: {}, 大小: {}", chunkFile.getName(), chunkFile.length());
                
                if (!chunkFile.exists() || !chunkFile.canRead()) {
                    log.error("分片文件不存在或无读取权限: {}", chunkFile.getAbsolutePath());
                    continue;
                }
                
                try (FileInputStream fis = new FileInputStream(chunkFile);
                     BufferedInputStream bis = new BufferedInputStream(fis)) {
                    
                    byte[] buffer = new byte[8192]; // 8KB缓冲区
                    int bytesRead;
                    while ((bytesRead = bis.read(buffer)) != -1) {
                        bos.write(buffer, 0, bytesRead);
                    }
                    bos.flush(); // 确保数据写入
                }
                
                // 删除已合并的分片
                boolean deleted = chunkFile.delete();
                log.info("删除分片文件: {}, 结果: {}", chunkFile.getName(), deleted);
            }
        } catch (IOException e) {
            log.error("文件合并失败: {}", e.getMessage(), e);
            // 如果合并失败，尝试删除可能创建的部分文件
            File partialFile = new File(finalFilePath);
            if (partialFile.exists()) {
                boolean deleted = partialFile.delete();
                log.info("删除部分合并文件: {}, 结果: {}", finalFilePath, deleted);
            }
            throw e; // 重新抛出异常
        }
        
        log.info("分片合并完成");

        // 验证文件是否成功创建
        File finalFile = new File(finalFilePath);
        log.info("验证最终文件是否存在: {}, 路径: {}", finalFile.exists(), finalFile.getAbsolutePath());
        
        if (!finalFile.exists()) {
            log.error("文件创建失败，最终文件不存在: {}", finalFilePath);
            // 检查目录权限和空间
            File parentDir = finalFile.getParentFile();
            log.info("父目录存在: {}, 可写: {}, 可用空间: {}", 
                     parentDir.exists(), 
                     parentDir.canWrite(), 
                     parentDir.getFreeSpace());
            throw new IOException("文件合并后未成功创建: " + finalFilePath);
        } else {
            log.info("文件成功创建，大小: {} 字节", finalFile.length());
        }

        // 删除临时目录
        File tempDirectory = new File(tempDir);
        boolean dirDeleted = tempDirectory.delete();
        log.info("删除临时目录: {}, 结果: {}", tempDir, dirDeleted);
        
        // 如果删除失败，尝试删除目录内容
        if (!dirDeleted) {
            File[] remainingFiles = tempDirFile.listFiles();
            if (remainingFiles != null) {
                log.info("临时目录中有 {} 个文件未删除", remainingFiles.length);
            }
        }

        // 计算文件大小 - 复用之前已经定义的finalFile变量
        long fileSize = finalFile.length();

        // 更新数据库中的文件信息
        if (fileEntity == null) {
            fileEntity = new FileEntity();
            fileEntity.setFileMd5(identifier);
        }

        fileEntity.setFileName(finalFile.getName());
        fileEntity.setOriginalName(fileName);
        fileEntity.setFileSize(fileSize);
        fileEntity.setContentType(contentType);
        fileEntity.setFilePath(finalFilePath);
        fileEntity.setUploadStatus(1); // 上传完成
        fileEntity.setUploadProgress(100.0);
        fileEntity.setUploadedChunks(fileEntity.getTotalChunks()); // 所有分片都已上传
        log.info("文件信息更新完成: 文件名={}, 文件路径={}, 大小={}", 
                 finalFile.getName(), finalFilePath, finalFile.length());
        fileEntity.setLastModified(new Date());

        // 清除上传进度缓存
        uploadProgressMap.remove(identifier);

        if (fileEntity.getId() != null) {
            fileMapper.updateById(fileEntity);
            // 更新分片信息的fileId
            updateChunkFileIds(identifier, fileEntity.getId());
        } else {
            fileMapper.insert(fileEntity);
            // 更新分片信息的fileId
            updateChunkFileIds(identifier, fileEntity.getId());
        }
        
        // 删除数据库中的分片信息，避免数据冗余
        log.info("删除已合并的分片记录: identifier={}", identifier);
        fileChunkService.deleteByIdentifier(identifier);
        
        return fileEntity;
    }

    @Override
    public List<FileEntity> listFiles() {
        return fileMapper.findAllFiles();
    }

    @Override
    public FileEntity getFileById(Long fileId) {
        return fileMapper.selectById(fileId);
    }

    @Override
    public FileEntity getFileByMd5(String fileMd5) {
        return fileMapper.findByFileMd5(fileMd5);
    }

    @Override
    public boolean checkFileExists(String fileMd5) {
        // 直接使用XML中定义的checkFileExists方法
        Boolean exists = fileMapper.checkFileExists(fileMd5);
        return exists != null && exists;
    }
    
    /**
     * 获取已上传的分片索引
     * 使用FileChunkService实现，替代直接的文件系统扫描
     */
    @Override
    public int[] checkUploadedChunks(String identifier) {
        try {
            return fileChunkService.getUploadedChunkIndexes(identifier);
        } catch (Exception e) {
            log.error("检查已上传分片失败: {}", e.getMessage(), e);
            // 降级到文件系统扫描作为备选方案
            return checkUploadedChunksFromFileSystem(identifier);
        }
    }
    
    @Override
    public void cleanupUploadChunks(String identifier) {
        try {
            log.info("开始清理上传分片，identifier: {}", identifier);
            
            if (identifier == null || identifier.isEmpty()) {
                log.error("标识符为空，无法清理分片");
                throw new IllegalArgumentException("标识符不能为空");
            }
            
            // 1. 清理数据库中的分片记录
            log.info("开始清理数据库中的分片记录");
            int deletedChunks = fileChunkService.deleteByIdentifier(identifier);
            log.info("数据库分片记录删除数量: {}", deletedChunks);
            
            // 2. 清理文件系统中的临时分片文件
            log.info("开始清理文件系统中的临时分片文件");
            // 使用Paths构建安全的路径，避免路径分隔符问题
            Path tempDirPath = Paths.get(uploadDir, "temp", identifier);
            String tempDir = tempDirPath.toString();
            File tempDirFile = tempDirPath.toFile();
            
            log.info("临时目录路径: {}", tempDir);
            log.info("临时目录是否存在: {}", tempDirFile.exists());
            
            if (tempDirFile.exists()) {
                log.info("临时目录存在，开始删除");
                boolean deleted = deleteDirectory(tempDirFile);
                log.info("临时分片目录删除结果: {}, 路径: {}", deleted, tempDir);
            } else {
                log.info("临时目录不存在，无需删除");
            }
            
            // 3. 清理上传进度缓存
            uploadProgressMap.remove(identifier);
            
            // 4. 删除相关的文件记录（如果存在但未完成上传的）
            FileEntity fileEntity = fileMapper.findByFileMd5(identifier);
            if (fileEntity != null && !fileEntity.getUploadStatus().equals("COMPLETED")) {
                fileMapper.deleteById(fileEntity.getId());
                log.info("删除未完成的文件记录，ID: {}", fileEntity.getId());
            }
            
            log.info("分片清理完成，identifier: {}", identifier);
        } catch (Exception e) {
            log.error("清理分片失败: {}", e.getMessage(), e);
            throw new RuntimeException("清理分片失败", e);
        }
    }

    // 已移除重复的checkUploadedChunks方法，使用fileMd5参数的版本已被identifier参数的版本替代
    
    /**
     * 从文件系统扫描已上传的分片
     * 作为数据库查询失败时的备选方案
     */
    private int[] checkUploadedChunksFromFileSystem(String fileMd5) {
        String tempDir = uploadDir + "temp/" + fileMd5 + "/";
        File directory = new File(tempDir);
        
        if (!directory.exists()) {
            return new int[0];
        }
        
        File[] chunkFiles = directory.listFiles();
        if (chunkFiles == null || chunkFiles.length == 0) {
            return new int[0];
        }
        
        // 收集已上传的分片索引
        int[] uploadedChunks = new int[chunkFiles.length];
        for (int i = 0; i < chunkFiles.length; i++) {
            uploadedChunks[i] = Integer.parseInt(chunkFiles[i].getName());
        }
        
        Arrays.sort(uploadedChunks);
        return uploadedChunks;
    }

    @Override
    public void downloadFile(Long fileId, HttpServletRequest request, HttpServletResponse response) throws IOException {
        FileEntity fileEntity = getFileById(fileId);
        if (fileEntity == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        File file = new File(fileEntity.getFilePath());
        if (!file.exists()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // 设置响应头，支持断点续传和暂停/恢复
        String contentType = fileEntity.getContentType() != null ? fileEntity.getContentType() : "application/octet-stream";
        response.setContentType(contentType);
        response.setCharacterEncoding("UTF-8");
        
        // 使用原始文件名，确保文件名正确编码
        String fileNameToEncode = fileEntity.getOriginalName() != null ? fileEntity.getOriginalName() : fileEntity.getFileName();
        String encodedFileName = java.net.URLEncoder.encode(fileNameToEncode, "UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
        log.info("下载文件: ID={}, 原始文件名={}, 编码后文件名={}", fileId, fileNameToEncode, encodedFileName);
        
        // 设置文件大小
        long fileSize = file.length();
        response.setHeader("Content-Length", String.valueOf(fileSize));
        
        // 必须设置Accept-Ranges头以支持断点续传
        response.setHeader("Accept-Ranges", "bytes");
        
        // 支持跨域，方便前端操作
        response.setHeader("Access-Control-Allow-Origin", "*");
        
        // 处理断点续传，支持暂停/恢复下载
        long start = 0;
        long end = fileSize - 1;

        // 解析Range请求头
        String rangeHeader = request.getHeader("Range");
        if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
            try {
                String[] range = rangeHeader.substring(6).split("-");
                if (!range[0].isEmpty()) {
                    start = Long.parseLong(range[0]);
                }
                if (range.length > 1 && !range[1].isEmpty()) {
                    end = Long.parseLong(range[1]);
                }
                
                // 验证范围的有效性
                if (start > end || start >= fileSize || end >= fileSize) {
                    response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    response.setHeader("Content-Range", "bytes */" + fileSize);
                    return;
                }
                
                // 设置部分内容响应状态
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
            } catch (NumberFormatException e) {
                log.error("无效的范围格式: {}", rangeHeader);
                // 忽略无效的范围请求，从头开始发送
            }
        }

        // 计算要发送的内容长度
        long contentLength = end - start + 1;
        response.setHeader("Content-Length", String.valueOf(contentLength));
        
        // 发送文件内容，使用缓冲流提高性能并支持暂停/恢复
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
             BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
            
            // 跳过已经下载的部分
            if (start > 0) {
                long skipped = bis.skip(start);
                if (skipped < start) {
                    log.error("无法跳过请求的字节数: 请求={}, 实际跳过={}", start, skipped);
                    throw new IOException("无法跳过请求的字节范围");
                }
            }
            
            // 使用较小的缓冲区，这样在暂停时可以更快地响应中断
            byte[] buffer = new byte[8192]; // 8KB缓冲区
            long bytesRead = 0;
            int currentRead;
            
            // 循环读取并发送文件内容，直到发送完请求的范围或发生中断
            while (bytesRead < contentLength && (currentRead = bis.read(buffer)) != -1) {
                // 计算本次应该写入的字节数，避免超出请求范围
                int bytesToWrite = (int) Math.min(currentRead, contentLength - bytesRead);
                bos.write(buffer, 0, bytesToWrite);
                bos.flush(); // 及时刷新缓冲区
                bytesRead += bytesToWrite;
                
                // 检查客户端是否断开连接（通过输出流状态判断）
                if (response.isCommitted()) {
                    try {
                        // 通过尝试刷新来检查连接状态
                        bos.flush();
                    } catch (IOException e) {
                        log.debug("客户端断开连接，停止下载");
                        break;
                    }
                }
            }
            
            log.info("文件下载完成: {}, 大小: {}, 范围: {}-{}", 
                     fileEntity.getOriginalName() != null ? fileEntity.getOriginalName() : fileEntity.getFileName(), fileSize, start, end);
        } catch (IOException e) {
            // 处理客户端主动取消下载的情况
            if (e.getMessage() != null && e.getMessage().contains("Broken pipe")) {
                log.debug("客户端取消了下载: {}", fileEntity.getOriginalName() != null ? fileEntity.getOriginalName() : fileEntity.getFileName());
            } else {
                log.error("文件下载出错: {}", e.getMessage());
                throw e;
            }
        }
    }

    @Override
    @Transactional
    public boolean deleteFile(Long fileId) throws IOException {
        FileEntity fileEntity = getFileById(fileId);
        if (fileEntity == null) {
            log.warn("尝试删除不存在的文件: fileId={}", fileId);
            return false;
        }

        // 删除物理文件
        File file = new File(fileEntity.getFilePath());
        if (file.exists() && !file.delete()) {
            log.error("无法删除文件: {}", fileEntity.getFilePath());
        }
        
        // 使用FileChunkService删除分片记录
        // 1. 首先按identifier删除（更准确）
        int deletedByIdentifier = 0;
        if (fileEntity.getIdentifier() != null) {
            deletedByIdentifier = fileChunkService.deleteByIdentifier(fileEntity.getIdentifier());
            log.info("通过identifier删除分片记录: identifier={}, 删除数量={}", fileEntity.getIdentifier(), deletedByIdentifier);
        }
        
        // 2. 然后按fileId删除（确保所有相关分片都被删除，即使identifier为空或不完整）
        int deletedByFileId = fileChunkService.deleteByFileId(fileId);
        log.info("通过fileId删除分片记录: fileId={}, 删除数量={}", fileId, deletedByFileId);
        
        // 删除临时分片目录（如果存在）
        if (fileEntity.getIdentifier() != null) {
            Path tempDirPath = Paths.get(uploadDir, "temp", fileEntity.getIdentifier());
            File tempDir = tempDirPath.toFile();
            if (tempDir.exists()) {
                boolean dirDeleted = deleteDirectory(tempDir);
                log.info("删除临时分片目录: {}, 结果: {}", tempDir.getAbsolutePath(), dirDeleted);
            }
        }

        // 从数据库中删除记录
        int deleteResult = fileMapper.deleteById(fileEntity.getId());
        log.info("删除文件记录: fileId={}, 文件名={}, 删除结果={}", 
                 fileId, fileEntity.getFileName(), deleteResult > 0);
        
        return deleteResult > 0;
    }
    
    /**
     * 递归删除目录
     */
    private boolean deleteDirectory(File directory) {
        if (directory == null || !directory.exists()) {
            log.warn("目录不存在或为空: {}", directory);
            return true;
        }
        
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    boolean subDirDeleted = deleteDirectory(file);
                    if (!subDirDeleted) {
                        log.error("无法删除子目录: {}", file.getAbsolutePath());
                    }
                } else {
                    boolean fileDeleted = false;
                    // 尝试多次删除，解决Windows文件锁定问题
                    for (int i = 0; i < 3; i++) {
                        if (file.delete()) {
                            fileDeleted = true;
                            log.debug("成功删除文件: {}", file.getAbsolutePath());
                            break;
                        } else {
                            log.warn("第{}次尝试删除文件失败: {}", (i+1), file.getAbsolutePath());
                            try {
                                // 短暂暂停后重试
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                            }
                        }
                    }
                    
                    if (!fileDeleted) {
                        log.error("多次尝试后仍无法删除文件: {}", file.getAbsolutePath());
                    }
                }
            }
        }
        
        boolean dirDeleted = false;
        // 尝试多次删除目录
        for (int i = 0; i < 3; i++) {
            if (directory.delete()) {
                dirDeleted = true;
                log.info("成功删除目录: {}", directory.getAbsolutePath());
                break;
            } else {
                log.warn("第{}次尝试删除目录失败: {}", (i+1), directory.getAbsolutePath());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        
        if (!dirDeleted) {
            log.error("多次尝试后仍无法删除目录: {}", directory.getAbsolutePath());
        }
        
        return dirDeleted;
    }

    @Override
    public String calculateFileMd5(MultipartFile file) throws IOException {
        try (InputStream inputStream = file.getInputStream()) {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                md.update(buffer, 0, bytesRead);
            }
            byte[] digest = md.digest();
            
            // 转换为十六进制字符串
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new IOException("计算文件MD5失败", e);
        }
    }

    /**
     * 更新上传进度
     */
    private void updateUploadProgress(String identifier, int chunkIndex, int totalChunks) {
        // 使用FileChunkService获取已上传的分片数，更准确
        int[] uploadedChunks = fileChunkService.getUploadedChunkIndexes(identifier);
        int uploadedCount = uploadedChunks.length;
        
        // 计算进度百分比
        double progress = (double) uploadedCount / totalChunks * 100;
        uploadProgressMap.put(identifier, Math.min(progress, 100.0));
    }
    
    /**
     * 计算分片文件的MD5值
     * @param filePath 分片文件路径
     * @return MD5值
     */
    private String calculateChunkMd5(Path filePath) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int bytesRead;
            try (InputStream inputStream = Files.newInputStream(filePath)) {
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    md.update(buffer, 0, bytesRead);
                }
            }
            byte[] digest = md.digest();
            
            // 转换为十六进制字符串
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            log.error("计算分片MD5失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 更新分片信息中的文件ID
     * 当文件实体创建后，将其ID关联到所有分片记录
     * 注意：在没有外键约束的情况下，此方法确保分片与文件的正确关联
     */
    @Transactional
    protected void updateChunkFileIds(String identifier, Long fileId) { 
         if (identifier == null || fileId == null) {
             log.warn("跳过更新分片文件ID: identifier或fileId为空");
             return;
         }
         
         try { 
             // 获取所有已上传的分片索引
             int[] chunkIndexes = fileChunkService.getUploadedChunkIndexes(identifier); 
             int successCount = 0;
             int failCount = 0;
             
             for (int index : chunkIndexes) { 
                 try {
                     FileChunk chunk = fileChunkService.getChunkByIdentifierAndIndex(identifier, index); 
                     if (chunk != null) { 
                         // 无论之前是否有fileId，都更新为最新的fileId
                         chunk.setFileId(fileId); 
                         boolean result = fileChunkService.saveChunk(chunk);
                         
                         if (result) {
                             successCount++;
                         } else {
                             failCount++;
                             log.error("更新单个分片文件ID失败: identifier={}, chunkIndex={}", identifier, index);
                         }
                     } else {
                         failCount++;
                         log.warn("未找到分片记录: identifier={}, chunkIndex={}", identifier, index);
                     }
                 } catch (Exception e) {
                     failCount++;
                     log.error("更新分片ID时发生异常: identifier={}, chunkIndex={}, 异常: {}", 
                             identifier, index, e.getMessage());
                 }
             } 
             
             // 记录更新结果
             log.info("分片文件ID更新完成: identifier={}, fileId={}, 总分片数={}, 成功={}, 失败={}", 
                     identifier, fileId, chunkIndexes.length, successCount, failCount);
             
             // 如果大部分更新失败，考虑抛出异常
             if (failCount > successCount && chunkIndexes.length > 0) {
                 throw new RuntimeException("分片文件ID更新失败率过高: " + failCount + " 个失败，" + successCount + " 个成功");
             }
         } catch (Exception e) { 
             log.error("更新分片文件ID失败: {}", e.getMessage(), e); 
             // 重新抛出异常以触发事务回滚
             if (e instanceof RuntimeException) {
                 throw e;
             }
             throw new RuntimeException("更新分片文件ID失败", e);
        } 
    }
    
    @Override
    public void cleanupChunks(String identifier) throws IOException {
        try {
            // 构建分片临时目录路径
            String tempDir = uploadDir + File.separator + "temp" + File.separator + identifier;
            File tempDirectory = new File(tempDir);
            
            // 清理临时分片文件
            if (tempDirectory.exists() && tempDirectory.isDirectory()) {
                boolean deleted = deleteDirectory(tempDirectory);
                log.info("清理临时分片目录: {}, 结果: {}", tempDir, deleted);
            }
            
            // 删除数据库中的分片记录
            int deletedCount = fileChunkService.deleteByIdentifier(identifier);
            log.info("删除数据库分片记录: identifier={}, 数量: {}", identifier, deletedCount);
            
        } catch (Exception e) {
                log.error("清理分片失败: {}", e.getMessage(), e);
                throw new IOException("清理分片失败: " + e.getMessage(), e);
            }
        }
        

    }